// scalaxx - Scala XML eXtensions
// (c) 2009-2010, Normen Müller, normen.mueller@gmail.com
// $Id: Differ.scala 82 2010-05-18 21:43:27Z normen.mueller $
package scalaxx.diff.j

import scala.xml.XML.{loadString}
import scala.xml.{NodeSeq, Node, MetaData}

import scaup.Views._
import scaup.eq._
import scaup.data.Conversions._

import scalaxx.Views._
import scalaxx.diff.{append, remove, update, before, elem, attr}
                                                                                        
/** Command bridge to Java
 * 
 *  @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 *  @version 0.0.4
 */
case class Command(val action: String, val path: String, val nodes: java.util.List[org.w3c.dom.Node])

/** Differencing algorithms bridge to Java
 * 
 *  @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 *  @version 0.0.4
 */
object Differ {
  import java.io.File
  import javax.xml.parsers._
  import javax.xml.transform._
  import javax.xml.transform.dom._
  import javax.xml.transform.stream._
  
  /** XML differencing algorithm using a default equivalence model.
   * 
   *  @param src Source file
   *  @param tgt Target file
   *  @return A list of modification commands on <code>src</code>.
   *  @throws TransformerException
   *  @throws ParserConfigurationException
   *  @throws TransformerConfigurationException
   */
  @throws(classOf[TransformerException])
  @throws(classOf[ParserConfigurationException])
  @throws(classOf[TransformerConfigurationException])
  def xmldiff(src: File, tgt: File): java.util.List[Command] = xmldiff(parse(src), parse(tgt))
  
  /** XML differencing algorithm using a default equivalence model.
   * 
   *  @param src Source DOM document
   *  @param tgt Target DOM document
   *  @return A list of modification commands on <code>src</code>.
   *  @throws TransformerException
   *  @throws ParserConfigurationException
   *  @throws TransformerConfigurationException
   */
  @throws(classOf[TransformerException])
  @throws(classOf[ParserConfigurationException])
  @throws(classOf[TransformerConfigurationException])
  def xmldiff(src: org.w3c.dom.Document, tgt: org.w3c.dom.Document): java.util.List[Command] = xmldiff(new BaseEquivalenceSystem, src, tgt)

  /** XML differencing algorithm using equivalence model <code>model</code>.
   * 
   *  @param model The equivalence model
   *  @param src Source file
   *  @param tgt Target file
   *  @return A list of modification commands on <code>src</code>.
   *  @throws TransformerException
   *  @throws ParserConfigurationException
   *  @throws TransformerConfigurationException
   */
  @throws(classOf[TransformerException])
  @throws(classOf[ParserConfigurationException])
  @throws(classOf[TransformerConfigurationException])
  def xmldiff[M <: MetaData, T >: Node](model: scaup.eq.EquivalenceSystem[M, T], src: File, tgt: File): java.util.List[Command] = xmldiff(model, parse(src), parse(tgt))
  
  /** XML differencing algorithm using equivalence model <code>model</code>.
   * 
   *  @param model The equivalence model
   *  @param src Source DOM document
   *  @param tgt Target DOM document
   *  @return A list of modification commands on <code>src</code>.
   *  @throws TransformerException
   *  @throws ParserConfigurationException
   *  @throws TransformerConfigurationException
   */
  @throws(classOf[TransformerException])
  @throws(classOf[ParserConfigurationException])
  @throws(classOf[TransformerConfigurationException])
  def xmldiff[M <: MetaData, T >: Node](model: scaup.eq.EquivalenceSystem[M, T], src: org.w3c.dom.Document, tgt: org.w3c.dom.Document): java.util.List[Command] = {
    scalaxx.diff.Differ.diff(model, loadString(convert(src)), loadString(convert(tgt))) map {
      case c: scalaxx.diff.Command =>  Command(c.prefix, c.path.toString, convert(c.instruction))
    }
  } 

  private def parse(f: File): org.w3c.dom.Document = try { DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(f) } catch { case e@_ => throw(e) }
  
  /* Note: Metadata is converted to a pair of text nodes.*/
  import scala.xml.Text
  private def convert(is: Seq[Instruction]): List[org.w3c.dom.Node] =
    ((Nil: List[org.w3c.dom.Node]) /: (is map {
      case elem(ns) => (ns map (_.toJdkNode(createDomDocument))).toList
      case attr(m)  => List(Text(m.key.toString()).toJdkNode(createDomDocument), Text(m.value.toString()).toJdkNode(createDomDocument))
    }))(_ ::: _)
  
  @throws(classOf[TransformerException])
  @throws(classOf[TransformerConfigurationException])
  private def convert(d: org.w3c.dom.Document): String =
    try {
      val trans = TransformerFactory.newInstance().newTransformer()
      trans.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes")
      trans.setOutputProperty(OutputKeys.INDENT, "yes")
      
      val sw = new java.io.StringWriter()
      trans.transform(new DOMSource(d), new StreamResult(sw))
      sw.toString()
    } catch { case e@_ => throw(e) }  
  
  @throws(classOf[ParserConfigurationException])
  private def createDomDocument(): org.w3c.dom.Document =
    try {
      val builder = DocumentBuilderFactory.newInstance().newDocumentBuilder()
      builder.newDocument()
    } catch { case e@_ => throw(e) }
}

//private[j] object DifferTest {
//  def main(args: Array[String]): Unit = {
//    val HOME = "/Users/nmueller".toFile
//    val cmds: java.util.List[Command] = scaup.j.Differ.xmldiff(parse(HOME/"omoc/src/difftst/data/tst1a.xml"),
//                                                               parse(HOME/"omoc/src/difftst/data/tst1b.xml"))    
//    cmds foreach (cmd => {
//      println("Action: " + cmd.action)
//      println("Path:   " + cmd.path)
//      cmd.nodes foreach (prettyPrintDom(_))
//    })
//  }
//  
//  import javax.xml.parsers._
//  import com.sun.org.apache.xml.internal.serialize._
//  
//  private def parse(f: java.io.File): org.w3c.dom.Document =
//    try {
//      DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(f)
//    } catch { case e@_ => throw(e) }
//  
//  private def prettyPrintDom(n: org.w3c.dom.Node) {    
//    val stringWriter = new java.io.StringWriter()
//    val serializer = new XMLSerializer(stringWriter, new OutputFormat(Method.XML, "UTF-8", true))
//    serializer.serialize(n)
//    println(stringWriter.toString())
//  }
//}
